/*
 *  MIDITriggersPage.java
 * 
 *  copyright (c) 2008, tom dinchak
 * 
 *  This file is part of pages.
 *
 *  pages is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  pages is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  You should have received a copy of the GNU General Public License
 *  along with pages; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */


package org.monome.pages;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.Receiver;
import javax.sound.midi.ShortMessage;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;

import org.apache.commons.lang.StringEscapeUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
* This code was edited or generated using CloudGarden's Jigloo
* SWT/Swing GUI Builder, which is free for non-commercial
* use. If Jigloo is being used commercially (ie, by a corporation,
* company or business for any purpose whatever) then you
* should purchase a license for each developer using Jigloo.
* Please visit www.cloudgarden.com for details.
* Use of Jigloo implies acceptance of these licensing terms.
* A COMMERCIAL LICENSE HAS NOT BEEN PURCHASED FOR
* THIS MACHINE, SO JIGLOO OR THIS CODE CANNOT BE USED
* LEGALLY FOR ANY CORPORATE OR COMMERCIAL PURPOSE.
*/
/**
 * The MIDI Triggers page.  Usage information is available at:
 * 
 * http://code.google.com/p/monome-pages/wiki/MIDITriggersPage
 *   
 * @author Tom Dinchak
 *
 */
public class MIDITriggersPage implements Page, ActionListener {

	/**
	 * Toggles mode constant
	 */
	private static final int MODE_TOGGLES = 0;

	/**
	 * Triggers mode constant
	 */
	private static final int MODE_TRIGGERS = 1;

	/**
	 * Rows orientation constant
	 */
	private static final int ORIENTATION_ROWS = 2;

	/**
	 * Columns orientation constant
	 */
	private static final int ORIENTATION_COLUMNS = 3;

	/**
	 * Checkboxes to enable toggle mode on or off for each row/col
	 */
	private JCheckBox[] toggles = new JCheckBox[16];

	/**
	 * The toggled state of each button (on or off)
	 */
	private int[][] toggleValues = new int[16][16];

	/**
	 * The MonomeConfiguration object this page belongs to
	 */
	MonomeConfiguration monome;

	/**
	 * The index of this page (the page number) 
	 */
	private int index;

	/**
	 * The GUI for this page
	 */
	private JPanel panel;

	/**
	 * the Add MIDI Output button 
	 */
	private JButton addMidiOutButton;

	/**
	 * Columns mode radio button
	 */
	private JRadioButton colRB;

	/**
	 * Rows mode radio button
	 */
	private JRadioButton rowRB;

	/**
	 * Rows/columns radio button group 
	 */
	private ButtonGroup rowColBG;

	/**
	 * The selected MIDI output device
	 */
	private Receiver recv;

	/**
	 * The name of the selected MIDI output device
	 */
	private String midiDeviceName;

	// GUI elements
	private JLabel row13Label;
	private JLabel row14Label;
	private JLabel row15Label;
	private JLabel row16Label;
	private JLabel row7Label;
	private JLabel row11Label;
	private JLabel row10Label;
	private JLabel row9Label;
	private JLabel row8Label;
	private JLabel row6Label;
	private JLabel row12Label;
	private JLabel row5Label;
	private JLabel row4Label;
	private JLabel jLabel1;
	private JLabel row3Label;
	private JLabel row2Label;
	private JLabel row1Label;
	
	// tilt stuff 
	private ADCOptions pageADCOptions = new ADCOptions();
		

	/**
	 * The name of the page 
	 */
	private String pageName = "MIDI Triggers";
	private JLabel pageNameLBL;
	
	/**
	 * @param monome The MonomeConfiguration this page belongs to
	 * @param index The index of this page (the page number)
	 */
	public MIDITriggersPage(MonomeConfiguration monome, int index) {
		this.monome = monome;
		this.index = index;

		for (int i=0; i < 16; i++) {
			toggles[i] = new JCheckBox();
			toggles[i].setText("Toggles");
		}
	}

	/* (non-Javadoc)
	 * @see org.monome.pages.Page#actionPerformed(java.awt.event.ActionEvent)
	 */
	public void actionPerformed(ActionEvent e) {
		System.out.println(e.getActionCommand());
		if (e.getActionCommand().equals("Set MIDI Output")) {
			String[] midiOutOptions = this.monome.getMidiOutOptions();
			String deviceName = (String)JOptionPane.showInputDialog(
					this.monome,
					"Choose a MIDI Output to add",
					"Add MIDI Output",
					JOptionPane.PLAIN_MESSAGE,
					null,
					midiOutOptions,
					"");

			if (deviceName == null) {
				return;
			}

			this.addMidiOutDevice(deviceName);
		} else if (e.getActionCommand().equals("Columns")) {
			row1Label.setText("Col 1");
			row2Label.setText("Col 2");
			row3Label.setText("Col 3");
			row4Label.setText("Col 4");
			row5Label.setText("Col 5");
			row6Label.setText("Col 6");
			row7Label.setText("Col 7");
			row8Label.setText("Col 8");
			row9Label.setText("Col 9");
			row10Label.setText("Col 10");
			row11Label.setText("Col 11");
			row12Label.setText("Col 12");
			row13Label.setText("Col 13");
			row14Label.setText("Col 14");
			row15Label.setText("Col 15");
			row16Label.setText("Col 16");
			this.redrawMonome();
		} else if (e.getActionCommand().equals("Rows")) {
			row1Label.setText("Row 1");
			row2Label.setText("Row 2");
			row3Label.setText("Row 3");
			row4Label.setText("Row 4");
			row5Label.setText("Row 5");
			row6Label.setText("Row 6");
			row7Label.setText("Row 7");
			row8Label.setText("Row 8");
			row9Label.setText("Row 9");
			row10Label.setText("Row 10");
			row11Label.setText("Row 11");
			row12Label.setText("Row 12");
			row13Label.setText("Row 13");
			row14Label.setText("Row 14");
			row15Label.setText("Row 15");
			row16Label.setText("Row 16");
			this.redrawMonome();
		}
	}

	/* (non-Javadoc)
	 * @see org.monome.pages.Page#addMidiOutDevice(java.lang.String)
	 */
	public void addMidiOutDevice(String deviceName) {
		this.recv = this.monome.getMidiReceiver(deviceName);
		this.midiDeviceName = deviceName;
		this.addMidiOutButton.removeActionListener(this);
		this.panel.removeAll();
		this.panel = null;			
		this.monome.redrawPanel();
	}
	
	/* (non-Javadoc)
	 * @see org.monome.pages.Page#getName()
	 */	
	public String getName() 
	{		
		return pageName;
	}
	/* (non-Javadoc)
	 * @see org.monome.pages.Page#setName()
	 */
	public void setName(String name) {
		this.pageName = name;
		this.pageNameLBL.setText("Page " + (this.index + 1) + ": " + pageName);
		this.monome.setJMenuBar(this.monome.createMenuBar());
	}

	/**
	 * Find out of toggle mode is enabled for a row/column.
	 * 
	 * @param index The index of the row/column
	 * @return The mode of the checkbox (toggles or triggers)
	 */
	private int getToggleMode(int index) {
		if (this.toggles[index].isSelected()) {
			return MODE_TOGGLES;
		} else {
			return MODE_TRIGGERS;
		}
	}

	/**
	 * Get the current orientation setting.
	 * 
	 * @return The current orientation (rows or columns)
	 */
	private int getOrientation() {
		// default to rows
		if (this.rowRB == null) {
			return ORIENTATION_ROWS;
		}
		if (this.rowRB.isSelected()) {
			return ORIENTATION_ROWS;
		} else {
			return ORIENTATION_COLUMNS;
		}
	}

	/* (non-Javadoc)
	 * @see org.monome.pages.Page#handlePress(int, int, int)
	 */
	public void handlePress(int x, int y, int value) {
		int a = x;
		int b = y;

		if (this.getOrientation() == ORIENTATION_COLUMNS) {
			a = y;
			b = x;
		}

		if (this.getToggleMode(b) == MODE_TOGGLES) {
			if (value == 1) {
				if (this.toggleValues[a][b] == 1) {
					this.toggleValues[a][b] = 0;
					this.monome.led(x, y, 0, this.index);
					// note on
				} else {
					this.toggleValues[a][b] = 1;
					this.monome.led(x, y, 1, this.index);
					// note off
				}
				this.playNote(a, b, 1);
				this.playNote(a, b, 0);					
			}
		} else {
			this.monome.led(x, y, value, this.index);
			this.playNote(a, b, value);
			// note on
			// note off
		}
	}

	/**
	 * Converts a button press into a MIDI note event
	 * 
	 * @param x The x value of the button pressed
	 * @param y The y value of the button pressed
	 * @param value The state, 1 = pressed, 0 = released
	 */
	public void playNote(int x, int y, int value) {
		int note_num = x + 12;
		int channel = y;
		int velocity = value * 127;
		ShortMessage note_out = new ShortMessage();
		try {
			if (velocity == 0) {
				note_out.setMessage(ShortMessage.NOTE_OFF, channel, note_num, velocity);
			} else {
				note_out.setMessage(ShortMessage.NOTE_ON, channel, note_num, velocity);
			}
		} catch (InvalidMidiDataException e) {
			e.printStackTrace();
		}
		if (this.recv != null) {
			this.recv.send(note_out, -1);
		}
	}

	/* (non-Javadoc)
	 * @see org.monome.pages.Page#handleReset()
	 */
	public void handleReset() {
		return;
	}

	/* (non-Javadoc)
	 * @see org.monome.pages.Page#handleTick()
	 */
	public void handleTick() {
		return;
	}

	/* (non-Javadoc)
	 * @see org.monome.pages.Page#redrawMonome()
	 */
	public void redrawMonome() {		
		for (int x = 0; x < this.monome.sizeX; x++) {
			for (int y = 0; y < this.monome.sizeY; y++) {
				int a = x;
				int b = y;
				if (this.getOrientation() == ORIENTATION_COLUMNS) {
					a = y;
					b = x;
				}
				if (this.getToggleMode(b) == MODE_TOGGLES) {
					if (this.toggleValues[a][b] == 1) {
						this.monome.led(x, y, 1, this.index);
					} else {
						this.monome.led(x, y, 0, this.index);
					}
				} else {
					this.monome.led(x, y, 0, this.index);
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.monome.pages.Page#send(javax.sound.midi.MidiMessage, long)
	 */
	public void send(MidiMessage message, long timeStamp) {
		ShortMessage msg = (ShortMessage) message;
		int x = msg.getData1() - 12;
		int y = msg.getChannel();
		if (x >= 0 && x < this.monome.sizeX && y >= 0 && y < this.monome.sizeY) {
			int velocity = msg.getData2();
			if (velocity == 127) {
				this.toggleValues[x][y] = 1;
			} else {
				this.toggleValues[x][y] = 0;
			}
			this.redrawMonome();
		}
		return;
	}

	/* (non-Javadoc)
	 * @see org.monome.pages.Page#toXml()
	 */
	public String toXml() {
		String mode;
		if (this.rowRB.isSelected()) {
			mode = "rows";
		} else {
			mode = "columns";
		}

		String xml = "";
		xml += "      <name>MIDI Triggers</name>\n";
		xml += "      <pageName>" + this.pageName + "</pageName>\n";
		xml += "      <selectedmidioutport>" + StringEscapeUtils.escapeXml(this.midiDeviceName) + "</selectedmidioutport>\n";		
		xml += "      <mode>" + mode + "</mode>\n";
		
		xml += "      <ccoffset>" + this.pageADCOptions.getCcOffset() + "</ccoffset>\n";
		xml += "      <sendADC>" + this.pageADCOptions.isSendADC() + "</sendADC>\n";
		xml += "      <midiChannelADC>" + this.pageADCOptions.getMidiChannel() + "</midiChannelADC>\n";
		xml += "      <adcTranspose>" + this.pageADCOptions.getAdcTranspose() + "</adcTranspose>\n";
		xml += "      <recv>" + this.pageADCOptions.getRecv() + "</recv>\n"; 	
		
		for (int i=0; i < 16; i++) {
			String state;
			if (this.toggles[i].isSelected()) {
				state = "on";
			} else {
				state = "off";
			}
			xml += "      <toggles>" + state + "</toggles>\n";
		}
		return xml;

	}

	/**
	 * @return The rows/columns radio button group
	 */
	private ButtonGroup getRowColBG() {
		if(rowColBG == null) {
			rowColBG = new ButtonGroup();
		}
		return rowColBG;
	}

	/* (non-Javadoc)
	 * @see org.monome.pages.Page#getPanel()
	 */
	public JPanel getPanel() {
		if (this.panel != null) {
			return this.panel;
		}

		JPanel panel = new JPanel();
		panel.setLayout(null);
		panel.setPreferredSize(new java.awt.Dimension(531, 180));

		pageNameLBL = new JLabel("Page " + (this.index + 1) + ": MIDI Triggers");

		row16Label = new JLabel();
		row16Label.setText("Row 16");
		row15Label = new JLabel();
		row15Label.setText("Row 15");
		row14Label = new JLabel();
		row14Label.setText("Row 14");
		row13Label = new JLabel();
		row13Label.setText("Row 13");
		rowRB = new JRadioButton();
		rowRB.setText("Rows");
		colRB = new JRadioButton();
		colRB.setText("Columns");

		this.getRowColBG().add(rowRB);
		this.getRowColBG().add(colRB);

		addMidiOutButton = new JButton();
		addMidiOutButton.addActionListener(this);
		panel.add(addMidiOutButton);
		panel.add(colRB);
		panel.add(rowRB);
		rowRB.setBounds(19, 104, 86, 18);
		colRB.setBounds(19, 123, 86, 18);
		addMidiOutButton.setText("Set MIDI Output");
		addMidiOutButton.setBounds(285, 120, 164, 21);
		row5Label = new JLabel();
		row5Label.setText("Row 5");
		row6Label = new JLabel();
		row6Label.setText("Row 6");
		row7Label = new JLabel();
		row7Label.setText("Row 7");
		row8Label = new JLabel();
		row8Label.setText("Row 8");
		row9Label = new JLabel();
		row9Label.setText("Row 9");
		row10Label = new JLabel();
		row10Label.setText("Row 10");
		row11Label = new JLabel();
		row11Label.setText("Row 11");
		row12Label = new JLabel();
		row12Label.setText("Row 12");
		panel.add(toggles[3]);
		toggles[3].setBounds(58, 78, 74, 18);
		row3Label = new JLabel();
		row3Label.setText("Row 3");
		row4Label = new JLabel();
		panel.add(row4Label);
		panel.add(toggles[7]);
		panel.add(row8Label);
		panel.add(toggles[11]);
		panel.add(row12Label);
		panel.add(toggles[15]);
		panel.add(row16Label);
		panel.add(toggles[2]);
		panel.add(row3Label);
		panel.add(toggles[6]);
		panel.add(row7Label);
		panel.add(toggles[10]);
		panel.add(row11Label);
		panel.add(toggles[14]);
		panel.add(row15Label);
		row15Label.setBounds(405, 60, 46, 14);
		toggles[14].setBounds(451, 58, 74, 18);
		row11Label.setBounds(274, 60, 46, 14);
		toggles[10].setBounds(320, 58, 74, 18);
		row7Label.setBounds(143, 60, 46, 14);
		toggles[6].setBounds(189, 58, 74, 18);
		row3Label.setBounds(12, 60, 46, 14);
		toggles[2].setBounds(58, 58, 74, 18);
		row16Label.setBounds(405, 80, 46, 14);
		toggles[15].setBounds(451, 78, 74, 18);
		row12Label.setBounds(274, 80, 46, 14);
		toggles[11].setBounds(320, 78, 74, 18);
		row8Label.setBounds(143, 80, 46, 14);
		toggles[7].setBounds(189, 78, 74, 18);
		row4Label.setText("Row 4");
		row4Label.setBounds(12, 80, 46, 14);
		row2Label = new JLabel();
		row2Label.setText("Row 2");
		panel.add(toggles[1]);
		panel.add(row2Label);
		panel.add(toggles[5]);
		panel.add(row6Label);
		panel.add(toggles[9]);
		panel.add(row10Label);
		panel.add(toggles[13]);
		panel.add(row14Label);
		row14Label.setBounds(405, 40, 46, 14);
		toggles[13].setBounds(451, 38, 74, 18);
		row10Label.setBounds(274, 40, 46, 14);
		toggles[9].setBounds(320, 38, 74, 18);
		row6Label.setBounds(143, 40, 46, 14);
		toggles[5].setBounds(189, 38, 74, 18);
		row2Label.setBounds(12, 40, 46, 14);
		toggles[1].setBounds(58, 38, 74, 18);
		row1Label = new JLabel();
		row1Label.setText("Row 1");
		panel.add(toggles[0]);
		panel.add(row1Label);
		panel.add(toggles[4]);
		panel.add(row5Label);
		panel.add(toggles[8]);
		panel.add(row9Label);
		panel.add(toggles[12]);
		panel.add(row13Label);
		panel.add(pageNameLBL);
		panel.add(getJLabel1());
		pageNameLBL.setBounds(0, 0, 189, 14);
		row13Label.setBounds(405, 20, 46, 14);
		toggles[12].setBounds(451, 18, 74, 18);
		row9Label.setBounds(274, 20, 46, 14);
		toggles[8].setBounds(320, 18, 74, 18);
		row5Label.setBounds(143, 20, 46, 14);
		toggles[4].setBounds(189, 18, 74, 18);
		row1Label.setBounds(12, 20, 63, 14);
		toggles[0].setBounds(58, 18, 74, 18);
		rowRB.setSelected(true);

		rowRB.addActionListener(this);
		colRB.addActionListener(this);
			
		
		this.panel = panel;
		return panel;
	}

	/**
	 * Sets the mode / orientation of the page to rows or columns mode
	 * 
	 * @param mode "rows" for row mode, "columns" for column mode
	 */
	public void setMode(String mode) {
		if (mode.equals("rows")) {
			this.rowRB.doClick();
		} else if (mode.equals("columns")) {
			this.colRB.doClick();
		}

	}

	/**
	 * Used when loading configuration to enable checkboxes for rows/columns that should be toggles.
	 * 
	 * @param l 
	 */
	public void enableToggle(int l) {
		this.toggles[l].doClick();
	}

	/* (non-Javadoc)
	 * @see org.monome.pages.Page#getCacheDisabled()
	 */
	public boolean getCacheDisabled() {
		return false;
	}

	/* (non-Javadoc)
	 * @see org.monome.pages.Page#destroyPage()
	 */
	public void destroyPage() {
		return;
	}
	
	private JLabel getJLabel1() {
		jLabel1 = new JLabel();
		jLabel1.setText("MIDI Out: " + this.midiDeviceName);
		jLabel1.setBounds(254, 0, 227, 14);
		return jLabel1;
	}
	
	
	public void clearPanel() {
		this.panel = null;
	}
	
	public void setIndex(int index) {
		this.index = index;
	}

	public void handleADC(int adcNum, float value) {
		if (this.pageADCOptions.isSendADC() && this.monome.adcObj.isEnabled()) {
			int midi = this.pageADCOptions.getMidiChannel();
			if(midi == -1) {
				this.monome.adcObj.sendCC(this.recv, 0, this.pageADCOptions.getCcADC(), monome, adcNum, value);
			}  else {
				this.monome.adcObj.sendCC(this.recv, midi, this.pageADCOptions.getCcADC(), monome, adcNum, value);
			}
		}
	}
	
	public void handleADC(float x, float y) {
		if (this.pageADCOptions.isSendADC() && this.monome.adcObj.isEnabled()) {
			int midi = this.pageADCOptions.getMidiChannel();
			if(midi == -1) {
				this.monome.adcObj.sendCC(this.recv, 0, this.pageADCOptions.getCcADC(), monome, x, y);
			} else {
				this.monome.adcObj.sendCC(this.recv, midi, this.pageADCOptions.getCcADC(), monome, x, y);
			}			
		}
	}
	public boolean isTiltPage() {
		return true;
	}
	public ADCOptions getAdcOptions() {
		return this.pageADCOptions;
	}

	public void setAdcOptions(ADCOptions options) { 
		this.pageADCOptions = options;
	}

	public void configure(Element pageElement) {		
		NodeList nameNL = pageElement.getElementsByTagName("pageName");
		Element el = (Element) nameNL.item(0);
		if (el != null) {
			NodeList nl = el.getChildNodes();
			String	name = ((Node) nl.item(0)).getNodeValue();
			this.setName(name);			
		}
				
		NodeList nl = pageElement.getElementsByTagName("mode");
		el = (Element) nl.item(0);
		if (el != null) {
			nl = el.getChildNodes();
			String mode = ((Node) nl.item(0)).getNodeValue();
			this.setMode(mode);
		}
		
		NodeList seqNL = pageElement.getElementsByTagName("toggles");
		for (int l=0; l < seqNL.getLength(); l++) {
			el = (Element) seqNL.item(l);
			if (el != null) {
				nl = el.getChildNodes();
				String mode = ((Node) nl.item(0)).getNodeValue();
				if (mode.equals("on")) {
					this.enableToggle(l);
				}
			}
		}		
		
		nl = pageElement.getElementsByTagName("ccoffset");
		el = (Element) nl.item(0);
		if (el != null) {
			nl = el.getChildNodes();
			String	ccOffset = ((Node) nl.item(0)).getNodeValue();
			this.pageADCOptions.setCcOffset(Integer.parseInt(ccOffset));
		}	
		
		nl = pageElement.getElementsByTagName("sendADC");
		el = (Element) nl.item(0);
		if (el != null) {
			nl = el.getChildNodes();
			String	sendADC = ((Node) nl.item(0)).getNodeValue();
			this.pageADCOptions.setSendADC(Boolean.parseBoolean(sendADC));
		}
		
		nl = pageElement.getElementsByTagName("adcTranspose");
		el = (Element) nl.item(0);
		if (el != null) {
			nl = el.getChildNodes();
			String	adcTranspose = ((Node) nl.item(0)).getNodeValue();
			this.pageADCOptions.setAdcTranspose(Integer.parseInt(adcTranspose));
		}
		
		nl = pageElement.getElementsByTagName("midiChannelADC");
		el = (Element) nl.item(0);
		if (el != null) {
			nl = el.getChildNodes();
			String	midiChannelADC = ((Node) nl.item(0)).getNodeValue();
			this.pageADCOptions.setMidiChannel(Integer.parseInt(midiChannelADC));
		}
		
		nl = pageElement.getElementsByTagName("recv");
		el = (Element) nl.item(0);
		if (el != null) {
			nl = el.getChildNodes();
			String	recv = ((Node) nl.item(0)).getNodeValue();
			this.pageADCOptions.setRecv(recv);
			if (recv != null) this.addMidiOutDevice(recv);
		}	
		
		this.redrawMonome();		
	}

	
}